package igloo.controller;

import igloo.Application;
import igloo.aws.IglooDynamoClient;
import igloo.aws.IglooGlacierClient;
import igloo.model.Archive;
import igloo.model.Igloo;
import igloo.model.Table;
import igloo.model.Vault;
import igloo.util.DateUtil;

import java.io.File;
import java.net.InetAddress;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.amazonaws.services.dynamodb.model.AttributeAction;
import com.amazonaws.services.dynamodb.model.AttributeValue;
import com.amazonaws.services.dynamodb.model.AttributeValueUpdate;
import com.amazonaws.services.dynamodb.model.Key;
import com.amazonaws.services.dynamodb.model.PutItemRequest;
import com.amazonaws.services.dynamodb.model.QueryRequest;
import com.amazonaws.services.dynamodb.model.QueryResult;
import com.amazonaws.services.dynamodb.model.UpdateItemRequest;
import com.amazonaws.services.glacier.model.DeleteArchiveRequest;
import com.amazonaws.services.glacier.transfer.ArchiveTransferManager;
import com.amazonaws.services.glacier.transfer.UploadResult;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;

public class VaultController
{
    private Vault mVault;
    private Map<Archive, ArchiveController> mArchiveControllers = new HashMap<Archive, ArchiveController>();

    public VaultController(Vault vault)
    {
        mVault = vault;
    }

    public Vault getVault()
    {
        return mVault;
    }

    public List<Archive> getArchives()
    {
        return mVault.getArchives();
    }

    public List<ArchiveController> getArchiveControllers()
    {
        List<ArchiveController> controllers = new LinkedList<ArchiveController>();
        for(ArchiveController c : mArchiveControllers.values())
        {
            controllers.add(c);
        }

        return controllers;
    }

    public ArchiveController getArchiveController(Archive archive)
    {
        return mArchiveControllers.get(archive);
    }

    public ListenableFuture<ArchiveController> createArchive(final File file, final String desc)
    {
        final SettableFuture<ArchiveController> future = SettableFuture.create();

        final IglooGlacierClient glacier = Application.getInstance()
                                                      .getGlacierClient(mVault.getRegion());
        final IglooDynamoClient dynamo = Application.getInstance().getDynamoClient();
        Application.getInstance().getExecutor().execute(new Runnable() {
            @Override
            public void run()
            {
                try
                {
                    ArchiveTransferManager atm = new ArchiveTransferManager(
                                                                            glacier.getClient(),
                                                                            glacier.getCredentials());
                    UploadResult result = atm.upload(mVault.getName(), desc, file);

                    Archive archive = new Archive();
                    archive.setDescription(desc);
                    archive.setHostName(InetAddress.getLocalHost().getHostName());
                    archive.setId(result.getArchiveId());
                    archive.setLocalUpdateDate(new Date());
                    archive.setName(file.getName());
                    archive.setPath(file.getAbsolutePath());
                    archive.setSize(file.length());
                    archive.setVault(mVault);

                    Map<String, AttributeValue> item = archive.toItem();
                    PutItemRequest request = new PutItemRequest(Table.ARCHIVE, item);
                    dynamo.putItem(request);

                    ArchiveController controller = new ArchiveController(archive);
                    mArchiveControllers.put(archive, controller);

                    mVault.addArchive(archive);

                    future.set(controller);
                }
                catch(Exception e)
                {
                    future.setException(e);
                }

            }
        });

        return future;
    }

    public void deleteArchive(Archive archive)
    {
        deleteArchive(archive.getId());
    }

    public void deleteArchive(String id)
    {
        DeleteArchiveRequest dar = new DeleteArchiveRequest(mVault.getName(), id);
        Application.getInstance().getGlacierClient(mVault.getRegion()).deleteArchive(dar);

        // We do not delete the archive from DynamoDB at this point, we only flag it as being delete.
        // This is because this archive will continue to appear in vault inventory snapshots until
        // a new snapshot is created. The archive will be remove from DynamoDB once it no longer
        // appears in the snapshot.
        AttributeValue hashKey = new AttributeValue(Vault.generateId(mVault.getRegion(),
                                                                     mVault.getName()));
        AttributeValue rangeKey = new AttributeValue(id);
        Key key = new Key(hashKey, rangeKey);

        Map<String, AttributeValueUpdate> update = new HashMap<String, AttributeValueUpdate>();
        update.put("deleted",
                   new AttributeValueUpdate(new AttributeValue().withN(String.valueOf("1")),
                                            AttributeAction.PUT));
        UpdateItemRequest uir = new UpdateItemRequest(Table.ARCHIVE, key, update);
        Application.getInstance().getDynamoClient().updateItem(uir);

        mVault.removeArchive(id);
    }

    /**
     * Asynchronously syncs igloo with DynamoDB
     * 
     * @return igloo future
     */
    public ListenableFuture<Igloo> sync()
    {
        final SettableFuture<Igloo> future = SettableFuture.create();

        Application.getInstance().getExecutor().execute(new Runnable() {
            @Override
            public void run()
            {
                QueryRequest request = new QueryRequest();
                request.setTableName(Table.ARCHIVE);
                request.setHashKeyValue(new AttributeValue().withS(Vault.generateId(mVault.getRegion(),
                                                                                    mVault.getName())));

                ListenableFuture<QueryResult> queryFuture = Application.getInstance()
                                                                       .getDynamoClient()
                                                                       .query(request);
                Futures.addCallback(queryFuture, new FutureCallback<QueryResult>() {
                    @Override
                    public void onSuccess(QueryResult result)
                    {
                        List<Map<String, AttributeValue>> items = result.getItems();
                        for(Map<String, AttributeValue> item : items)
                        {
                            if(!item.containsKey("deleted"))
                            {
                                Archive archive = toArchive(item);
                                ArchiveController controller = new ArchiveController(archive);
                                mArchiveControllers.put(archive, controller);
                                mVault.addArchive(archive);
                            }
                        }
                    }

                    @Override
                    public void onFailure(Throwable t)
                    {
                        t.printStackTrace();
                    }
                }, Application.getInstance().getExecutor());
            }
        });

        return future;
    }

    public Archive toArchive(Map<String, AttributeValue> item)
    {
        Archive archive = new Archive();

        if(item.containsKey("desc"))
        {
            archive.setDescription(item.get("desc").getS());
        }

        if(item.containsKey("host"))
        {
            archive.setHostName(item.get("host").getS());
        }

        archive.setId(item.get("id").getS());

        if(item.containsKey("localUpdate"))
        {
            archive.setLocalUpdateDate(DateUtil.parse(item.get("localUpdate").getS()));
        }

        archive.setName(item.get("name").getS());

        archive.setPath(item.get("path").getS());

        if(item.containsKey("remoteUpdate"))
        {
            archive.setRemoteUpdateDate(DateUtil.parse(item.get("remoteUpdate").getS()));
        }

        archive.setSize(Integer.parseInt(item.get("size").getN()));

        archive.setVault(mVault);

        return archive;
    }
}
